module OpenAccount
    require 'digest/sha1'

    # mix this class into a user model to get login functionality
    module OpenAccountModel

        def self.included(base)
            base.class_eval do

                # Virtual attribute for the unencrypted password
                validates_presence_of     :login, :email, :if => :not_openid?
                validates_presence_of     :password,                   :if => :password_required?
                validates_presence_of     :password_confirmation,      :if => :password_required?
                validates_length_of       :password, :within => 4..40, :if => :password_required?
                validates_confirmation_of :password,                   :if => :password_required?
                validates_length_of       :login,    :within => 3..40, :if => :not_openid?
                
                validates_length_of       :email,    :within => 3..100, :if => :not_openid?
                validates_uniqueness_of   :login, :case_sensitive => false
                validates_uniqueness_of   :email, :allow_blank => true
                
                before_create :make_activation_code
            end
            base.extend(ClassMethods)
        end

        module ClassMethods

            # Authenticates a user by their login name and unencrypted password.  Returns the user or nil.
            def authenticate(login, password)
                u = find :first, :conditions => ['login = ? and activated_at IS NOT NULL', login] # need to get the salt
                u && u.authenticated?(password, u.salt) ? u : nil
                return nil if u.nil?                
                u.logged_in_at = Time.now.utc
                u.save(false) # don't validate.
                u
            end
            
            # authenticate the user via security token TODO finish working out how to do this
            def authenticate_by_api_key(key)
                logged_in_user = find(:first, :conditions => ["api_key = ?", key])
                return nil if logged_in_user.nil?
                logged_in_user.logged_in_at = Time.now.utc
                logged_in_user.save(false) # don't validate.
                logged_in_user
            end
        end


        def authenticated?(password, salt)
            crypted_password == salt_the_password(password, salt)
        end
        
        
        # 'password' is a virtual attribute.  Do all password validation here.
        def password
            @password
        end

        def password=(password_value)
            @password = password_value
            self.salt = make_salt
            self.crypted_password = self.salt_the_password(salt, self.hashed(@password))
        end

        # Activates the user in the database.
        def activate
            @activated = true
            self.activated_at = Time.now.utc
            self.activation_code = nil
            self.save(false) # don't validate.
        end

        def forget_me
            self.remember_token_expires_at = nil
            self.remember_token            = nil
            save(false)
        end

        protected

        # password
        def hashed(hash_it)
            # check if a salt has been set...
            if OpenAccount::OpenAccountConfiguration.config(:salt) == nil
                raise "You must define a :salt value in the configuration for the AccountConfiguration module."
            end

            return Digest::SHA1.hexdigest("#{OpenAccount::OpenAccountConfiguration.config(:salt)}--#{hash_it}--}")[0..39]
        end

        def make_salt
            self.hashed("salt-#{Time.now}")
        end

        def salt_the_password(salt, hashed_password)
            hashed(salt + hashed_password)
        end

        def activated?
            !! activation_code.nil?
        end

        # Returns true if the user has just been activated.
        def recently_activated?
            @activated
        end


        def remember_token?
            remember_token_expires_at && Time.now.utc < remember_token_expires_at
        end

        # These create and unset the fields required for remembering users between browser closes
        def remember_me
            remember_me_for 2.weeks
        end

        def remember_me_for(time)
            remember_me_until time.from_now.utc
        end

        def remember_me_until(time)
            self.remember_token_expires_at = time
            self.remember_token            = encrypt("#{email}--#{remember_token_expires_at}")
            save(false)
        end

        def password_required?
            not_openid? && (crypted_password.blank? || !password.blank?)
        end

        def not_openid?
            identity_url.blank?
        end

        def make_activation_code
            self.activation_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
        end

        def reset_password
            # First update the password_reset_code before setting the
            # reset_password flag to avoid duplicate email notifications.
            update_attributes(:password_reset_code => nil)
            @reset_password = true
        end

        def recently_reset_password?
            @reset_password
        end

        def forgot_password
            @forgotten_password = true
            reset_code = self.make_password_reset_code
            self.update_attribute(:password_reset_code, reset_code)
            return reset_code
        end

        def recently_forgot_password?
            @forgotten_password
        end

        def generate_api_key
            self.api_key = self.hashed("key-#{Time.now}" + rand.to_s)
            self.update_attribute(:api_key, self.api_key)
            return self.api_key
        end

        def make_password_reset_code
            self.password_reset_code = Digest::SHA1.hexdigest( Time.now.to_s.split(//).sort_by {rand}.join )
        end

        # store the amount of time that the security token is valid for
        def token_lifetime
            Time.at(Time.now.to_i + OpenAccount::OpenAccountConfiguration.config(:security_token_life_days) * 60 * 60 * 24)
        end

    end
end
